/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.binding;

import org.apache.ibatis.annotations.Flush;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 */

/**
 * MapperMethod 主要作用就是 处理 Mapper 接口的函数的注解、参数和返回值。
 * 主要功能：执行 SQL语句相关操作
 * MapperMethod 必须提供 Mapper 接口的路径
 */
public class MapperMethod {

  private final SqlCommand command;         // 用于获取 SQL 语句类型、 Mapper的 Id 等信息
  private final MethodSignature method;       // 方法签名

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 初始化 SqlCommand   Sql 命令
    this.command = new SqlCommand(config, mapperInterface, method);
    // 初始化方法签名
    this.method = new MethodSignature(config, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // Sql 命令类型
    if (SqlCommandType.INSERT == command.getType()) {
      // 获取参数信息
      Object param = method.convertArgsToSqlCommandParam(args);
      // 先调用 sqlSession的 insert方法，得到行数（int）
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  private Object rowCountResult(int rowCount) {
    final Object result;
    // 方法返回值是否是 Void
    if (method.returnsVoid()) {
      result = null;      // 无返回结果
    }// 如果 返回结果是 Integer
    else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = Integer.valueOf(rowCount);
    }// 如果 返回结果是 Long
    else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = Long.valueOf(rowCount);
    }// 如果返回结果 Boolean
    else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = Boolean.valueOf(rowCount > 0);
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName() 
          + " needs either a @ResultMap annotation, a @ResultType annotation," 
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

  private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
    Object collection = config.getObjectFactory().create(method.getReturnType());
    MetaObject metaObject = config.newMetaObject(collection);
    metaObject.addAll(list);
    return collection;
  }

  @SuppressWarnings("unchecked")
  private <E> E[] convertToArray(List<E> list) {
    E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
    array = list.toArray(array);
    return array;
  }

  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }

  public static class ParamMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -2212268410512043556L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }

  }

  /**
   * 静态内部类  Sql指令
   * 封装了具体执行的动作
   */
  public static class SqlCommand {

    private final String name;            // xml 标签的 ID   Mapper Id
    private final SqlCommandType type;    // Sql指令类型  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 拿到全限定类名 + 方法名，例 ： custom.po.Student.study
      String statementName = mapperInterface.getName() + "." + method.getName();
      // MappedStatement 对象 封装一个 Mapper 接口对应的 SQL 操作
      MappedStatement ms = null;
      // 从 configuration 中查找是否有这个方法的全限定类名的名称， 如果有直接方法的全限定类名名称来获取 MappedStatement
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
        // method方法向上获取父类的全限定类名称  parentStatementName
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        // 在从 configuration中获取 MappedStatement
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        // 如果这个方法上有 Flush 注解
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): " + statementName);
        }
      } else {
        // 这个 ms.getId()就是 mapper.xml中配置的 一条 sql语句中设置的 id属性值。
        name = ms.getId();
        // sql指令的类型
        type = ms.getSqlCommandType();
        // 判断 sql 标签类型，未知就抛异常
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }
  }

  /**
   * 静态内部类  MethodSignature
   *  这个内部类主要用于处理函数的参数、注解和返回值。
   *  方法签名： 封装了接口当中方法的参数类型，返回值类型，等信息
   */
  public static class MethodSignature {

    private final boolean returnsMany;        // 是否返回多条结果
    private final boolean returnsMap;         // 返回值是否是 Map
    private final boolean returnsVoid;        // 返回值是否是 Void
    private final Class<?> returnType;        // 返回值类型
    private final String mapKey;
    private final Integer resultHandlerIndex; //resultHandler类型参数的位置
    private final Integer rowBoundsIndex;     //rowBound类型参数的位置
    private final SortedMap<Integer, String> params;//用来存储参数信息
    private final boolean hasNamedParameters;  // 是否存在命名参数

    public MethodSignature(Configuration configuration, Method method) {
      this.returnType = method.getReturnType();
      this.returnsVoid = void.class.equals(this.returnType);
      // 对象工厂 判断  returnType是否是集合类型，来判断返回值是否 是多条
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      // 判断方法上是否有 @Param 注解
      this.hasNamedParameters = hasNamedParams(method);
      // 找到  RowBounds 类型的参数所在的位置
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      // 找到 ResultHandler 类型的参数所在的位置
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 存储参数信息
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

    /**
     * 创建SqlSession 对象需要传递的参数逻辑
     * args是用户mapper所传递的方法参数列表， 如果方法没有参数，则返回null
     * 如果方法只包含一个参数并且不包含命名参数， 则返回传递的参数值
     * 如果包含多个参数或包含命名参数，则返回包含名字和对应值的map对象
     * @param args  mapper 传递的方法参数列表
     * @return
     */
    public Object convertArgsToSqlCommandParam(Object[] args) {
      final int paramCount = params.size();   // 参数数量
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasNamedParameters && paramCount == 1) {    //没有命名参数并且 只有一个参数
        return args[params.keySet().iterator().next().intValue()];
      } else {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey().intValue()]);
          // issue #71, add param names as param1, param2...but ensure backward compatibility
          final String genericParamName = "param" + String.valueOf(i + 1);
          if (!param.containsKey(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

    public boolean hasRowBounds() {
      return rowBoundsIndex != null;
    }

    public RowBounds extractRowBounds(Object[] args) {
      return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
    }

    public boolean hasResultHandler() {
      return resultHandlerIndex != null;
    }

    public ResultHandler extractResultHandler(Object[] args) {
      return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
    }

    public String getMapKey() {
      return mapKey;
    }

    public Class<?> getReturnType() {
      return returnType;
    }

    public boolean returnsMany() {
      return returnsMany;
    }

    public boolean returnsMap() {
      return returnsMap;
    }

    public boolean returnsVoid() {
      return returnsVoid;
    }

    /**
     * method 方法中   paramType 类型的参数所在 方法参数集合中的 索引位置
     * @param method
     * @param paramType
     * @return
     */
    private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
      Integer index = null;
      // 方法参数类型集合
      final Class<?>[] argTypes = method.getParameterTypes();
      for (int i = 0; i < argTypes.length; i++) {
        // paramType 和 argTypes[i] 匹配
        if (paramType.isAssignableFrom(argTypes[i])) {
          if (index == null) {
            // 这个参数所在的位置
            index = i;
          } else {
            throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
        }
      }
      return index;
    }

    private String getMapKey(Method method) {
      String mapKey = null;
      if (Map.class.isAssignableFrom(method.getReturnType())) {
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) {
          mapKey = mapKeyAnnotation.value();
        }
      }
      return mapKey;
    }

    /**
     * 该函数的作用是把方法的参数处理成对应位置和名称的集合
     * @param method          例：custom.po.Student.study( @Param("123")String.class,Object.class,Integer.class)
     *
     * @param hasNamedParameters    是否有 @Param 注解
     * @return
     */
    private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
      final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
      // 获取方法的所有参数的类型   -> [String.class , Object.class , Integer.class]
      final Class<?>[] argTypes = method.getParameterTypes();
      // 遍历参数类型
      for (int i = 0; i < argTypes.length; i++) {
        // 如果参数不是 RowBounds 和 不是 ResultHandler 类型   如果是就不做处理
        if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
          String paramName = String.valueOf(params.size());
          // 如果是 名称 参数 @Param
          if (hasNamedParameters) {
            paramName = getParamNameFromAnnotation(method, i, paramName);
          }
          params.put(i, paramName);
        }
      }
      /**
       * SortedMap<Integer,String> params = new TreeMap<>();
       * params = {0=123, 1=1, 2=2 , 4=3}
       */
      return params;
    }

    private String getParamNameFromAnnotation(Method method, int i, String paramName) {
      // 一个方法中，某个参数前可以有多个注解
      // 位置 i 参数下 的注解，，[]
      final Object[] paramAnnos = method.getParameterAnnotations()[i];
      for (Object paramAnno : paramAnnos) {
        if (paramAnno instanceof Param) {
          // 获取 @Param 注解的 value 值
          paramName = ((Param) paramAnno).value();
          break;
        }
      }
      return paramName;
    }

    /**
     * 这个方法主要判断 方法上是否有 @Param 注解
     * @param method
     * @return
     */
    private boolean hasNamedParams(Method method) {
      // 获取参数对应的注解
      final Object[][] paramAnnos = method.getParameterAnnotations();
      for (Object[] paramAnno : paramAnnos) {
        for (Object aParamAnno : paramAnno) {
          // 判断这些注解是否属于 Param 注解    instanceof Param
          if (aParamAnno instanceof Param) {
            return true;
          }
        }
      }
      return false;
    }

  }

}
